EventBridge SchedulerでSecrets Managerのシークレットを毎分ローテーションさせてみた
ひょっとして毎分ローテーションさせることもできるのでは
こんにちは、のんピ(@non____97)です。
皆さんはSecrets Managerに保存したシークレットを毎分ローテーションさせたいと思ったことはありますか? 私はあります。
つい先日、Secrets Managerが4時間毎にシークレットをローテーションできるようになりました。
今までの最短ローテーション間隔は1日だったので、待ち望んでいた方は多いのではないでしょうか。
しかし、「4時間と言わず、毎分ローテーションさせたいんだ...!!」という方もいるに違いありません。
そんな思いを馳せていると、ふと、最近リリースされたEventBridge Schedulerは最短1分間隔でAWSのAPIを直接呼び出すことができることを思い出しました。
なんだか、EventBridge Schedulerを組み合わせることで毎分シークレットをローテーションできる気がしてきました。
やってみたので紹介します。
検証環境
検証環境は以下の通りです。
Secrets Managerで管理しているRDS DBインスタンスのadminユーザーの認証情報をEventBridge Schedulerで毎分ローテーションさせます。
ちなみに、パブリックサブネットがありますが一切出番はありません。当初Lambda関数をパブリックサブネットに配置してSecrets Managerのエンドポイントにアクセスしようと思っていましたが、Lambda関数のENIには直接パブリックIPアドレスは割り当てられないようです。
プライベートリソースにアクセスするには、関数をプライベートサブネットに接続します。関数にインターネットアクセスが必要な場合は、ネットワークアドレス変換 (NAT) を使用します。関数をパブリックサブネットに接続しても、インターネットアクセスやパブリック IP アドレスは提供されません。
検証環境は全てAWS CDKで定義しています。使用したコードは以下リポジトリに保存しています。
Secrets Managerのシークレットの最短ローテーション間隔が4時間であることを確認する
まず、Secrets Managerのシークレットの最短ローテーション間隔が4時間であることを確認します。
AWS CDKで4時間毎にRDS DBインスタンスのadminユーザーの認証情報をローテーションするよう定義します。
// Secrets Manager const instanceIdentifier = "rds-db-instance"; const dbAdminSecret = new cdk.aws_secretsmanager.Secret( this, "DB Admin Secret", { secretName: `/rds/${instanceIdentifier}/admin`, generateSecretString: { excludeCharacters: " %+~`#$&*()|[]{}:;<>?!'/@\"\\", generateStringKey: "password", passwordLength: 32, requireEachIncludedType: true, secretStringTemplate: '{"username": "admin"}', }, } ); // RDS DB Instance const dbInstance = new cdk.aws_rds.DatabaseInstance( this, "RDS DB Instance", { engine: cdk.aws_rds.DatabaseInstanceEngine.mysql({ version: cdk.aws_rds.MysqlEngineVersion.VER_8_0_30, }), vpc, allocatedStorage: 20, availabilityZone: vpc.availabilityZones[0], backupRetention: cdk.Duration.days(0), credentials: cdk.aws_rds.Credentials.fromSecret(dbAdminSecret), instanceIdentifier, instanceType: cdk.aws_ec2.InstanceType.of( cdk.aws_ec2.InstanceClass.T3, cdk.aws_ec2.InstanceSize.MICRO ), multiAz: false, publiclyAccessible: false, storageEncrypted: true, subnetGroup, securityGroups: [dbSG], } ); // Set DB Instance storage type to gp3 const cfnDbInstance = dbInstance.node .defaultChild as cdk.aws_rds.CfnDBInstance; cfnDbInstance.addPropertyOverride("StorageType", "gp3"); // Rotation Secret new cdk.aws_secretsmanager.SecretRotation( this, "Rotation DB Admin Secret", { application: cdk.aws_secretsmanager.SecretRotationApplication .MYSQL_ROTATION_SINGLE_USER, secret: dbAdminSecret, target: dbInstance, vpc, excludeCharacters: dbAdminSecret.excludeCharacters, vpcSubnets: vpc.selectSubnets({ subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED, }), } ); const cfnDbAdminSecretRotationSchedule = dbAdminSecret.node.tryFindChild( "RotationSchedule" )?.node.defaultChild as cdk.aws_secretsmanager.CfnRotationSchedule; cfnDbAdminSecretRotationSchedule.rotationRules = { scheduleExpression: "cron(0 /4 * * ? *)", };
L2 ConstructであるSecretRotationは、AWS CDK v2.51.1時点では日単位でしかローテーションの間隔を指定できません。そのため、escape hatchesでL1 ConstuctのCfnRotationScheduleで4時間毎ローテーションするようにCron式を渡してあげています。
Secrets Managerのローテーションで扱うCron式は一般的なCron式とは少し異なるので、以下AWS公式ドキュメントを参考しながら定義すると良いと思います。
こちらのコードでスタックをデプロイすると、以下のようにシークレットが作成されます。
手動でシークレットをローテーションさせてみます。
Secrets Managerのコンソールから対象のシークレットを選択してすぐにシークレットをローテーションさせる
をクリックします。確認のウィンドウが表示されるのでローテーションさせる
をクリックします。
その後、シークレットをローテーションさせるLambda関数が出力したログをCloudWatch Logsから確認すると、以下のようにfinishSecret
まで正常に完了していることが分かります。
START RequestId: ff4db62c-50fc-4ebd-a844-9a01cbb23ca4 Version: $LATEST [INFO] 2022-11-26T04:34:37.805Z ff4db62c-50fc-4ebd-a844-9a01cbb23ca4 createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 409c87ba-96c6-4be9-8467-cd73b09e00bd. END RequestId: ff4db62c-50fc-4ebd-a844-9a01cbb23ca4 REPORT RequestId: ff4db62c-50fc-4ebd-a844-9a01cbb23ca4 Duration: 599.37 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: ea4e3a59-680f-42aa-8005-01edf928541d Version: $LATEST [INFO] 2022-11-26T04:34:38.561Z ea4e3a59-680f-42aa-8005-01edf928541d Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T04:34:38.580Z ea4e3a59-680f-42aa-8005-01edf928541d setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: ea4e3a59-680f-42aa-8005-01edf928541d REPORT RequestId: ea4e3a59-680f-42aa-8005-01edf928541d Duration: 770.83 ms Billed Duration: 771 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 02b75c05-015c-473d-8ba3-ffa49c3fcbba Version: $LATEST [INFO] 2022-11-26T04:34:39.083Z 02b75c05-015c-473d-8ba3-ffa49c3fcbba Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T04:34:39.100Z 02b75c05-015c-473d-8ba3-ffa49c3fcbba testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 02b75c05-015c-473d-8ba3-ffa49c3fcbba REPORT RequestId: 02b75c05-015c-473d-8ba3-ffa49c3fcbba Duration: 496.23 ms Billed Duration: 497 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: e0315a05-6ca2-4cec-9285-9909a903792a Version: $LATEST [INFO] 2022-11-26T04:34:39.439Z e0315a05-6ca2-4cec-9285-9909a903792a finishSecret: Successfully set AWSCURRENT stage to version 409c87ba-96c6-4be9-8467-cd73b09e00bd for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: e0315a05-6ca2-4cec-9285-9909a903792a REPORT RequestId: e0315a05-6ca2-4cec-9285-9909a903792a Duration: 295.90 ms Billed Duration: 296 ms Memory Size: 128 MB Max Memory Used: 74 MB
createSecret
やsetSecret
などシークレットをローテーションさせる仕組みは以下AWS公式ドキュメントが参考になります。
次に、Cron式にcron(0 /3 * * ? *)
を指定して、3時間毎ローテーションさせようとしてみます。AWS CDKでスタックを更新しようとした時のログは以下の通りです。
# 差分を確認 > npx cdk diff Stack RdsStack Resources [~] AWS::SecretsManager::RotationSchedule DB Admin Secret/RotationSchedule DBAdminSecretRotationScheduleD9E65D4C └─ [~] RotationRules └─ [~] .ScheduleExpression: ├─ [-] cron(0 /4 * * ? *) └─ [+] cron(0 /3 * * ? *) # スタックのデプロイ > npx cdk deploy ✨ Synthesis time: 8.15s RdsStack: building assets... [0%] start: Building 1698fc2a042155d9d58b710d388ae3870ae49f076459e185131ab516be75ed1c:current_account-current_region [100%] success: Built 1698fc2a042155d9d58b710d388ae3870ae49f076459e185131ab516be75ed1c:current_account-current_region RdsStack: assets built RdsStack: deploying... [0%] start: Publishing 1698fc2a042155d9d58b710d388ae3870ae49f076459e185131ab516be75ed1c:current_account-current_region [100%] success: Published 1698fc2a042155d9d58b710d388ae3870ae49f076459e185131ab516be75ed1c:current_account-current_region RdsStack: creating CloudFormation changeset... 13:37:23 | UPDATE_FAILED | AWS::SecretsManager::RotationSchedule | DBAdminSecretRotationScheduleD9E65D4C Hourly rotation period must be at least 4 hours. (Service: AWSSecretsManager; Status Code: 400; Error Code: InvalidPara meterException; Request ID: 4858af05-e3eb-4e10-826f-2b8a08d8a810; Proxy: null) ❌ RdsStack failed: Error: The stack named RdsStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Hourly rotation period must be at least 4 hours. (Service: AWSSecretsManager; Status Code: 400; Error Code: InvalidParameterException; Request ID: 4858af05-e3eb-4e10-826f-2b8a08d8a810; Proxy: null) at FullCloudFormationDeployment.monitorDeployment (/<ディレクトリパス>/rds/node_modules/aws-cdk/lib/api/deploy-stack.ts:505:13) at processTicksAndRejections (node:internal/process/task_queues:95:5) at deployStack2 (/<ディレクトリパス>/rds/node_modules/aws-cdk/lib/cdk-toolkit.ts:264:24) at /<ディレクトリパス>/rds/node_modules/aws-cdk/lib/deploy.ts:39:11 at run (/<ディレクトリパス>/rds/node_modules/p-queue/dist/index.js:163:29) ❌ Deployment failed: Error: Stack Deployments Failed: Error: The stack named RdsStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Hourly rotation period must be at least 4 hours. (Service: AWSSecretsManager; Status Code: 400; Error Code: InvalidParameterException; Request ID: 4858af05-e3eb-4e10-826f-2b8a08d8a810; Proxy: null) at deployStacks (/<ディレクトリパス>/rds/node_modules/aws-cdk/lib/deploy.ts:61:11) at processTicksAndRejections (node:internal/process/task_queues:95:5) at CdkToolkit.deploy (/<ディレクトリパス>/rds/node_modules/aws-cdk/lib/cdk-toolkit.ts:338:7) at initCommandLine (/<ディレクトリパス>/rds/node_modules/aws-cdk/lib/cli.ts:364:12) Stack Deployments Failed: Error: The stack named RdsStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Hourly rotation period must be at least 4 hours. (Service: AWSSecretsManager; Status Code: 400; Error Code: InvalidParameterException; Request ID: 4858af05-e3eb-4e10-826f-2b8a08d8a810; Proxy: null)
Hourly rotation period must be at least 4 hours.
と怒られました。
マネジメントコンソールからスケジュール式ビルダーで4時間未満にしようとしても怒られました。
スケジュール式でも同様に怒られます。
Secrets Managerの自動ローテーション機能としては、ローテーション間隔が4時間未満のスケジュールは受け付けないことが分かります。
EventBridge Schedulerでシークレットを毎分ローテーションさせる
AWS CDKで定義してみる
それでは毎分シークレットをローテーションさせるEventBridge Schedulerの作成をします。
仕組みとしてはSecrets ManagerのRotateSecret APIを毎分呼ぶといったものです。
EventBridge SchedulerについてもAWS CDKで定義します。しかし、AWS CDK v2.51.1時点ではEventBridge SchedulerのL2 Constructはありません。そのため、L1 ConstuctであるCfnScheduleで定義します。
実質CloudFormationなので、AWS::Scheduler::Scheduleの説明を確認しながら定義します。
実際のコードは以下の通りです。
// EventBridge Scheduler IAM Role const schedulerIamRole = new cdk.aws_iam.Role(this, "Scheduler IAM Role", { assumedBy: new cdk.aws_iam.ServicePrincipal("scheduler.amazonaws.com"), managedPolicies: [ new cdk.aws_iam.ManagedPolicy( this, "Rotate DB Admin Secret IAM Policy", { statements: [ new cdk.aws_iam.PolicyStatement({ effect: cdk.aws_iam.Effect.ALLOW, actions: ["secretsmanager:RotateSecret"], resources: [dbAdminSecret.secretArn], }), ], } ), ], }); // EventBridge Scheduler new cdk.aws_scheduler.CfnSchedule( this, "Rotate DB Admin Secret Every Minutes", { flexibleTimeWindow: { mode: "OFF", }, scheduleExpression: "cron(* * * * ? *)", target: { arn: "arn:aws:scheduler:::aws-sdk:secretsmanager:rotateSecret", roleArn: schedulerIamRole.roleArn, input: `{ "SecretId": "${dbAdminSecret.secretArn}" }`, retryPolicy: { maximumEventAgeInSeconds: 60, maximumRetryAttempts: 0, }, }, description: "Rotate DB Admin Secret Every Minutes", name: "rotate-db-admin-secret-every-minutes", scheduleExpressionTimezone: "Asia/Tokyo", state: "ENABLED", } );
スタックを更新すると、以下のようにEventBridge Schedulerが作成されていました。「毎分実行してやるぞ」という気概を感じます。
AWS CLIからEventBridge Schedulerを確認すると以下のようになります。
$ aws scheduler get-schedule --name rotate-db-admin-secret-every-minutes { "Arn": "arn:aws:scheduler:us-east-1:<AWSアカウントID>:schedule/default/rotate-db-admin-secret-every-minutes", "CreationDate": "2022-11-26T05:51:28.923000+00:00", "Description": "Rotate DB Admin Secret Every Minutes", "FlexibleTimeWindow": { "Mode": "OFF" }, "GroupName": "default", "LastModificationDate": "2022-11-26T05:59:08.943000+00:00", "Name": "rotate-db-admin-secret-every-minutes", "ScheduleExpression": "cron(* * * * ? *)", "ScheduleExpressionTimezone": "Asia/Tokyo", "State": "ENABLED", "Target": { "Arn": "arn:aws:scheduler:::aws-sdk:secretsmanager:rotateSecret", "Input": "{ \"SecretId\": \"arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ\" }", "RetryPolicy": { "MaximumEventAgeInSeconds": 60, "MaximumRetryAttempts": 0 }, "RoleArn": "arn:aws:iam::<AWSアカウントID>:role/RdsStack-SchedulerIAMRole5E381F6D-1JRE81M9AI8AK" } }
毎分ローテーションされていることを確認
それでは毎分ローテーションされていることを確認します。
数分ほど待ってから、シークレットをローテーションさせるLambda関数の呼び出された回数(Invocations)をCloudWatchメトリクスから確認します。
期間を1分にしていますが、途切れることなく毎分呼び出されていますね。
CloudWatch Logsに出力されたログも確認してみましょう。
START RequestId: df67a260-e1bd-4ab4-9656-9fb3718338d3 Version: $LATEST [INFO] 2022-11-26T05:52:39.117Z df67a260-e1bd-4ab4-9656-9fb3718338d3 Found credentials in environment variables. [INFO] 2022-11-26T05:52:40.350Z df67a260-e1bd-4ab4-9656-9fb3718338d3 createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 1ae8ef17-73f4-4abe-8d78-13b0fec61397. END RequestId: df67a260-e1bd-4ab4-9656-9fb3718338d3 REPORT RequestId: df67a260-e1bd-4ab4-9656-9fb3718338d3 Duration: 1483.07 ms Billed Duration: 1484 ms Memory Size: 128 MB Max Memory Used: 72 MB Init Duration: 320.78 ms START RequestId: 01ff4e8c-2074-4f10-9fd4-3939a5508a01 Version: $LATEST [INFO] 2022-11-26T05:52:41.096Z 01ff4e8c-2074-4f10-9fd4-3939a5508a01 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:52:41.100Z 01ff4e8c-2074-4f10-9fd4-3939a5508a01 setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 01ff4e8c-2074-4f10-9fd4-3939a5508a01 REPORT RequestId: 01ff4e8c-2074-4f10-9fd4-3939a5508a01 Duration: 755.97 ms Billed Duration: 756 ms Memory Size: 128 MB Max Memory Used: 73 MB START RequestId: 75844a0e-bb0a-405c-899e-b3c441359d2f Version: $LATEST [INFO] 2022-11-26T05:52:41.597Z 75844a0e-bb0a-405c-899e-b3c441359d2f Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:52:41.615Z 75844a0e-bb0a-405c-899e-b3c441359d2f testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 75844a0e-bb0a-405c-899e-b3c441359d2f REPORT RequestId: 75844a0e-bb0a-405c-899e-b3c441359d2f Duration: 475.74 ms Billed Duration: 476 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: f2a078c4-3e6b-4687-8d75-812655352769 Version: $LATEST [INFO] 2022-11-26T05:52:41.956Z f2a078c4-3e6b-4687-8d75-812655352769 finishSecret: Successfully set AWSCURRENT stage to version 1ae8ef17-73f4-4abe-8d78-13b0fec61397 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: f2a078c4-3e6b-4687-8d75-812655352769 REPORT RequestId: f2a078c4-3e6b-4687-8d75-812655352769 Duration: 311.57 ms Billed Duration: 312 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: ad17c261-939d-440d-9653-d17d59695c1b Version: $LATEST [INFO] 2022-11-26T05:53:38.769Z ad17c261-939d-440d-9653-d17d59695c1b createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 849977bc-2454-45ac-b105-dca7dcdb4eb4. END RequestId: ad17c261-939d-440d-9653-d17d59695c1b REPORT RequestId: ad17c261-939d-440d-9653-d17d59695c1b Duration: 549.74 ms Billed Duration: 550 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: bd774f1b-ce94-49fb-b90d-f17ad6ee2efb Version: $LATEST [INFO] 2022-11-26T05:53:39.538Z bd774f1b-ce94-49fb-b90d-f17ad6ee2efb Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:53:39.567Z bd774f1b-ce94-49fb-b90d-f17ad6ee2efb setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: bd774f1b-ce94-49fb-b90d-f17ad6ee2efb REPORT RequestId: bd774f1b-ce94-49fb-b90d-f17ad6ee2efb Duration: 792.68 ms Billed Duration: 793 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 88e9485a-c378-4062-90d6-2d94f757fffe Version: $LATEST [INFO] 2022-11-26T05:53:40.060Z 88e9485a-c378-4062-90d6-2d94f757fffe Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:53:40.076Z 88e9485a-c378-4062-90d6-2d94f757fffe testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 88e9485a-c378-4062-90d6-2d94f757fffe REPORT RequestId: 88e9485a-c378-4062-90d6-2d94f757fffe Duration: 497.60 ms Billed Duration: 498 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 24b93cec-5544-4f3b-9a34-7336ca3e0a92 Version: $LATEST [INFO] 2022-11-26T05:53:40.416Z 24b93cec-5544-4f3b-9a34-7336ca3e0a92 finishSecret: Successfully set AWSCURRENT stage to version 849977bc-2454-45ac-b105-dca7dcdb4eb4 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 24b93cec-5544-4f3b-9a34-7336ca3e0a92 REPORT RequestId: 24b93cec-5544-4f3b-9a34-7336ca3e0a92 Duration: 327.68 ms Billed Duration: 328 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 074a6d17-4b26-4f18-a057-cd962de7316f Version: $LATEST [INFO] 2022-11-26T05:54:38.770Z 074a6d17-4b26-4f18-a057-cd962de7316f createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 83ea7947-a5c5-4ff5-b113-e0fea2fcaa78. END RequestId: 074a6d17-4b26-4f18-a057-cd962de7316f REPORT RequestId: 074a6d17-4b26-4f18-a057-cd962de7316f Duration: 528.22 ms Billed Duration: 529 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 33e601cd-2547-412f-860b-b1e51a744df6 Version: $LATEST [INFO] 2022-11-26T05:54:39.517Z 33e601cd-2547-412f-860b-b1e51a744df6 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:54:39.528Z 33e601cd-2547-412f-860b-b1e51a744df6 setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 33e601cd-2547-412f-860b-b1e51a744df6 REPORT RequestId: 33e601cd-2547-412f-860b-b1e51a744df6 Duration: 735.61 ms Billed Duration: 736 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: c6f482b5-f943-41bc-98d8-0fef818414df Version: $LATEST [INFO] 2022-11-26T05:54:40.021Z c6f482b5-f943-41bc-98d8-0fef818414df Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:54:40.037Z c6f482b5-f943-41bc-98d8-0fef818414df testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: c6f482b5-f943-41bc-98d8-0fef818414df REPORT RequestId: c6f482b5-f943-41bc-98d8-0fef818414df Duration: 516.74 ms Billed Duration: 517 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 259dc9fa-e1bf-43cc-b8b9-5ccafbfe0d5b Version: $LATEST [INFO] 2022-11-26T05:54:40.377Z 259dc9fa-e1bf-43cc-b8b9-5ccafbfe0d5b finishSecret: Successfully set AWSCURRENT stage to version 83ea7947-a5c5-4ff5-b113-e0fea2fcaa78 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 259dc9fa-e1bf-43cc-b8b9-5ccafbfe0d5b REPORT RequestId: 259dc9fa-e1bf-43cc-b8b9-5ccafbfe0d5b Duration: 312.58 ms Billed Duration: 313 ms Memory Size: 128 MB Max Memory Used: 74 MB START RequestId: 7a34fef5-f95f-4ed7-9641-31af28e5cb1c Version: $LATEST [INFO] 2022-11-26T05:55:38.813Z 7a34fef5-f95f-4ed7-9641-31af28e5cb1c createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 1941b61a-020a-4676-aba3-6265e9ba5a07. END RequestId: 7a34fef5-f95f-4ed7-9641-31af28e5cb1c REPORT RequestId: 7a34fef5-f95f-4ed7-9641-31af28e5cb1c Duration: 561.22 ms Billed Duration: 562 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 8bc0e12a-70f0-4a1e-baca-ad9630f35d8f Version: $LATEST [INFO] 2022-11-26T05:55:39.539Z 8bc0e12a-70f0-4a1e-baca-ad9630f35d8f Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:55:39.564Z 8bc0e12a-70f0-4a1e-baca-ad9630f35d8f setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 8bc0e12a-70f0-4a1e-baca-ad9630f35d8f REPORT RequestId: 8bc0e12a-70f0-4a1e-baca-ad9630f35d8f Duration: 730.85 ms Billed Duration: 731 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: dd52d184-055f-4b3b-8f7f-ad60721cdd5d Version: $LATEST [INFO] 2022-11-26T05:55:40.040Z dd52d184-055f-4b3b-8f7f-ad60721cdd5d Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:55:40.042Z dd52d184-055f-4b3b-8f7f-ad60721cdd5d testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: dd52d184-055f-4b3b-8f7f-ad60721cdd5d REPORT RequestId: dd52d184-055f-4b3b-8f7f-ad60721cdd5d Duration: 484.30 ms Billed Duration: 485 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: fd0bb6b4-382c-4714-803d-7db3f8c12ff5 Version: $LATEST [INFO] 2022-11-26T05:55:40.437Z fd0bb6b4-382c-4714-803d-7db3f8c12ff5 finishSecret: Successfully set AWSCURRENT stage to version 1941b61a-020a-4676-aba3-6265e9ba5a07 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: fd0bb6b4-382c-4714-803d-7db3f8c12ff5 REPORT RequestId: fd0bb6b4-382c-4714-803d-7db3f8c12ff5 Duration: 324.36 ms Billed Duration: 325 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 9a5380e5-934f-4123-a0b9-28d5cfab2607 Version: $LATEST [INFO] 2022-11-26T05:56:39.032Z 9a5380e5-934f-4123-a0b9-28d5cfab2607 createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 2450c778-8c43-41f5-8c32-a858b66bfe2e. END RequestId: 9a5380e5-934f-4123-a0b9-28d5cfab2607 REPORT RequestId: 9a5380e5-934f-4123-a0b9-28d5cfab2607 Duration: 831.60 ms Billed Duration: 832 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 39974d0a-6f6f-4747-bcc2-a100375ec45a Version: $LATEST [INFO] 2022-11-26T05:56:39.797Z 39974d0a-6f6f-4747-bcc2-a100375ec45a Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:56:39.803Z 39974d0a-6f6f-4747-bcc2-a100375ec45a setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 39974d0a-6f6f-4747-bcc2-a100375ec45a REPORT RequestId: 39974d0a-6f6f-4747-bcc2-a100375ec45a Duration: 762.55 ms Billed Duration: 763 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 01f0c644-2ad9-4930-aadc-8038e8325f2e Version: $LATEST [INFO] 2022-11-26T05:56:40.278Z 01f0c644-2ad9-4930-aadc-8038e8325f2e Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T05:56:40.280Z 01f0c644-2ad9-4930-aadc-8038e8325f2e testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 01f0c644-2ad9-4930-aadc-8038e8325f2e REPORT RequestId: 01f0c644-2ad9-4930-aadc-8038e8325f2e Duration: 430.84 ms Billed Duration: 431 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: b99fb5d8-a9ee-493f-8e77-877880fc9237 Version: $LATEST [INFO] 2022-11-26T05:56:40.618Z b99fb5d8-a9ee-493f-8e77-877880fc9237 finishSecret: Successfully set AWSCURRENT stage to version 2450c778-8c43-41f5-8c32-a858b66bfe2e for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: b99fb5d8-a9ee-493f-8e77-877880fc9237 REPORT RequestId: b99fb5d8-a9ee-493f-8e77-877880fc9237 Duration: 316.11 ms Billed Duration: 317 ms Memory Size: 128 MB Max Memory Used: 75 MB
毎分40秒ごろにシークレットがローテーションされていそうです。
0秒ちょうどに実行されないのはEventBridgeルールと同じなので注意しましょう。
Eventbridge のスケジュール式は、秒レベルの精度ではありません。cron 式を使用した最小の粒度は 1 分です。EventBridge とターゲットサービスは分散しているため、スケジュールされたルールがトリガーされてからターゲットサービスがターゲットリソースを実行するまでの間に数秒の遅延が発生することもあります。
スケジュールに従って実行する Amazon EventBridge ルールの作成 - Amazon EventBridge
CloudTrailからも毎分RotateSecret APIが呼び出されていることを確認します。
14:52から15:05まで毎分呼び出されていることが分かります。
最後にローテーションされたシークレットを確認します。
# 6:15:25時点のシークレット > date Sat Nov 26 06:15:25 UTC 2022 > aws secretsmanager get-secret-value \ --secret-id /rds/rds-db-instance/admin \ | jq -r .SecretString {"password": "U8AK=JYadmU_9nKmgXvvT,zC.Kihg9Vr", "engine": "mysql", "port": 3306, "dbInstanceIdentifier": "rds-db-instance", "host": "rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com", "username": "admin"} # 6:15:40時点のシークレット > date Sat Nov 26 06:15:40 UTC 2022 > aws secretsmanager get-secret-value \ --secret-id /rds/rds-db-instance/admin \ | jq -r .SecretString {"password": "-ub3i0Bee10TYfnQT4h.=uWUjHe^34MK", "engine": "mysql", "port": 3306, "dbInstanceIdentifier": "rds-db-instance", "host": "rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com", "username": "admin"}
確かにpassword
が変更されていますね。
シークレットのバージョンを見ても1分毎に新しいバージョンが作成されていることが分かります。
aws secretsmanager list-secret-version-ids \ --secret-id /rds/rds-db-instance/admin { "Versions": [ { "VersionId": "01ae4546-106a-4cdc-b43f-52ef4f3d4e93", "VersionStages": [ "AWSCURRENT", "AWSPENDING" ], "LastAccessedDate": "2022-11-26T00:00:00+00:00", "CreatedDate": "2022-11-26T06:16:38.788000+00:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] }, { "VersionId": "4358e8c2-6899-4656-9c0d-d599423ce04a", "VersionStages": [ "AWSPREVIOUS" ], "LastAccessedDate": "2022-11-26T00:00:00+00:00", "CreatedDate": "2022-11-26T06:15:38.796000+00:00", "KmsKeyIds": [ "DefaultEncryptionKey" ] } ], "ARN": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ", "Name": "/rds/rds-db-instance/admin" }
自動ローテーションを無効化した状態でEventBridge SchedulerからRotateSecret APIを毎分呼ぶ
最後に、自動ローテーションを無効化した状態でEventBridge SchedulerからRotateSecret APIを毎分呼んでみます。
EventBridge SchedulerからのローテーションのタイミングとSecrets Managerの自動ローテーション機能によるローテーションのタイミングがバッティングすると、同じタイミングでシークレットを更新しようとして処理に失敗する可能性があります。そのようなことを防ぐために、自動ローテーションを無効化して、その状態でEventBridge SchedulerからRotateSecret APIを毎分呼べるかどうか確認してみます。
それでは、Secrets Managerのコンソールからシークレットの自動ローテーションを無効化します。
無効化するとLambda関数が表示されなくなり、手動でローテーションすることができなくなりました。
このまま1分ほど待つと、無効にしたはずの自動ローテーションが有効になっていました。
Lambda関数のログを確認する限り、問題なくローテーションできていそうです。
START RequestId: 4dc15e73-0f47-47ec-ac41-1ba1329e3801 Version: $LATEST [INFO] 2022-11-26T08:13:38.755Z 4dc15e73-0f47-47ec-ac41-1ba1329e3801 createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version ad13c9cf-3769-47a0-8093-54b948d26932. END RequestId: 4dc15e73-0f47-47ec-ac41-1ba1329e3801 REPORT RequestId: 4dc15e73-0f47-47ec-ac41-1ba1329e3801 Duration: 531.82 ms Billed Duration: 532 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 06a3e5f4-7c09-4319-ba18-e3ccd67357f8 Version: $LATEST [INFO] 2022-11-26T08:13:39.519Z 06a3e5f4-7c09-4319-ba18-e3ccd67357f8 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T08:13:39.526Z 06a3e5f4-7c09-4319-ba18-e3ccd67357f8 setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 06a3e5f4-7c09-4319-ba18-e3ccd67357f8 REPORT RequestId: 06a3e5f4-7c09-4319-ba18-e3ccd67357f8 Duration: 772.67 ms Billed Duration: 773 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 68a77858-7a1c-4456-a148-12acd9d00b63 Version: $LATEST [INFO] 2022-11-26T08:13:40.040Z 68a77858-7a1c-4456-a148-12acd9d00b63 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T08:13:40.042Z 68a77858-7a1c-4456-a148-12acd9d00b63 testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 68a77858-7a1c-4456-a148-12acd9d00b63 REPORT RequestId: 68a77858-7a1c-4456-a148-12acd9d00b63 Duration: 465.30 ms Billed Duration: 466 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: f8b54322-697d-4959-a970-d573d435f5a9 Version: $LATEST [INFO] 2022-11-26T08:13:40.380Z f8b54322-697d-4959-a970-d573d435f5a9 finishSecret: Successfully set AWSCURRENT stage to version ad13c9cf-3769-47a0-8093-54b948d26932 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: f8b54322-697d-4959-a970-d573d435f5a9 REPORT RequestId: f8b54322-697d-4959-a970-d573d435f5a9 Duration: 331.19 ms Billed Duration: 332 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: f95de13e-4ab3-4d66-9bb9-24be698c194e Version: $LATEST [INFO] 2022-11-26T08:14:38.851Z f95de13e-4ab3-4d66-9bb9-24be698c194e createSecret: Successfully put secret for ARN arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ and version 8e73dec5-8f0e-4148-a7ae-ae6aa8930bb3. END RequestId: f95de13e-4ab3-4d66-9bb9-24be698c194e REPORT RequestId: f95de13e-4ab3-4d66-9bb9-24be698c194e Duration: 606.23 ms Billed Duration: 607 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 01cf483d-09cd-42a3-b738-b585c9f416f9 Version: $LATEST [INFO] 2022-11-26T08:14:39.622Z 01cf483d-09cd-42a3-b738-b585c9f416f9 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T08:14:39.641Z 01cf483d-09cd-42a3-b738-b585c9f416f9 setSecret: Successfully set password for user admin in MySQL DB for secret arn arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 01cf483d-09cd-42a3-b738-b585c9f416f9 REPORT RequestId: 01cf483d-09cd-42a3-b738-b585c9f416f9 Duration: 793.25 ms Billed Duration: 794 ms Memory Size: 128 MB Max Memory Used: 75 MB START RequestId: 2ef13d07-cae7-41c4-b4c4-2db5e575dd19 Version: $LATEST [INFO] 2022-11-26T08:14:40.143Z 2ef13d07-cae7-41c4-b4c4-2db5e575dd19 Successfully established SSL/TLS connection as user 'admin' with host: 'rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com' [INFO] 2022-11-26T08:14:40.161Z 2ef13d07-cae7-41c4-b4c4-2db5e575dd19 testSecret: Successfully signed into MySQL DB with AWSPENDING secret in arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 2ef13d07-cae7-41c4-b4c4-2db5e575dd19 REPORT RequestId: 2ef13d07-cae7-41c4-b4c4-2db5e575dd19 Duration: 495.56 ms Billed Duration: 496 ms Memory Size: 128 MB Max Memory Used: 76 MB START RequestId: 2e66bc47-386b-4455-bec8-f06f4eedaa3c Version: $LATEST [INFO] 2022-11-26T08:14:40.581Z 2e66bc47-386b-4455-bec8-f06f4eedaa3c finishSecret: Successfully set AWSCURRENT stage to version 8e73dec5-8f0e-4148-a7ae-ae6aa8930bb3 for secret arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ. END RequestId: 2e66bc47-386b-4455-bec8-f06f4eedaa3c REPORT RequestId: 2e66bc47-386b-4455-bec8-f06f4eedaa3c Duration: 362.40 ms Billed Duration: 363 ms Memory Size: 128 MB Max Memory Used: 76 MB
再度自動ローテーションを無効化して、AWS CLIからシークレットを確認してみます。
$ aws secretsmanager describe-secret \ --secret-id /rds/rds-db-instance/admin { "ARN": "arn:aws:secretsmanager:us-east-1:<AWSアカウントID>:secret:/rds/rds-db-instance/admin-Y3UjnJ", "Name": "/rds/rds-db-instance/admin", "RotationEnabled": false, "RotationLambdaARN": "arn:aws:lambda:us-east-1:<AWSアカウントID>:function:RdsStackRotationDBAdminSecretF7491082", "RotationRules": { "AutomaticallyAfterDays": 1, "ScheduleExpression": "cron(0 /4 * * ? *)" }, "LastRotatedDate": "2022-11-26T08:17:40.589000+00:00", "LastChangedDate": "2022-11-26T08:21:09.427000+00:00", "LastAccessedDate": "2022-11-26T00:00:00+00:00", "Tags": [ { "Key": "aws:cloudformation:stack-name", "Value": "RdsStack" }, { "Key": "aws:cloudformation:logical-id", "Value": "DBAdminSecret46986D78" }, { "Key": "aws:cloudformation:stack-id", "Value": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/RdsStack/047d16a0-6d3e-11ed-b366-0e68edb56a2f" } ], "VersionIdsToStages": { "3bb9982d-2306-4924-a244-13d34e0faafb": [ "AWSCURRENT", "AWSPENDING" ], "7108cedd-be29-48ed-ad8f-3f3c9df1f011": [ "AWSPREVIOUS" ] }, "CreatedDate": "2022-11-26T03:54:52.473000+00:00" }
マネジメントコンソールでは表示されませんが、ローテーションで使用するLambda関数やCron式の情報は保持されたままのようですね。
RotateSecret APIのドキュメントを見てみます。
If you include the configuration parameters, the operation sets the values for the secret and then immediately starts a rotation. If you don't include the configuration parameters, the operation starts a rotation with the values already stored in the secret.
(以下機械翻訳)
設定パラメータを含めると、操作は秘密の値を設定した後、すぐにローテーションを開始します。設定パラメータを含めない場合、操作はすでにsecretに格納されている値でローテーションを開始します。
どうやら、RotateSecret APIを叩くと保存されている設定を元にローテーションするようです。その副作用で自動ローテーションが有効になるみたいですね。
EventBridge Schedulerに無限の可能性を感じる
EventBridge SchedulerでSecrets Managerのシークレットを毎分ローテーションさせてみました。
EventBridge Schedulerに無限の可能性を感じますね。ただし、今回の検証の構成は、シークレットのローテーションのタイミングが重複した場合のリスクに対応できていないため、参考にするとしても、よく検証をした上でお願いします。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!